﻿// cl -c http_transmit.c
// link -dll -out:libhttptransmit.dll http_transmit.obj

//g++ -O2 -fPIC -c http_transmit.cpp

// mkdir tmp
// cd tmp
// ar x /usr/local/lib/A.a
// ar x /usr/local/lib/B.a
// ar x /usr/local/lib/C.a
// ar cru libABC.a *.o
// ranlib libABC.a


#ifdef _MSC_VER
#define NOMINMAX
#endif   // _MSC_VER

#include <cstring>
#include <string>
#include <thread>
#include <mutex>

#include "curl/curl.h"

#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#define DEFAULT_CON_TIMEOUT_MS 15000

std::once_flag _curl_init_flag;

typedef struct userdata_st
{
    char*         buff;
    unsigned int* pos;
    unsigned int  size;
} userdata_st;

static size_t write_userdata(void* ptr, size_t size, size_t nmemb, void* stream)
{
    userdata_st* ud = (userdata_st*)stream;
    if (ud->size != 0)
    {
        unsigned int len = (unsigned int)(size * nmemb);
        if (*ud->pos + len <= ud->size)
        {
            memcpy(ud->buff + *ud->pos, ptr, len);
            *ud->pos += len;
            ud->buff[*ud->pos] = 0;
        }
        else
        {
            if (*ud->pos < ud->size - 1)
            {
                len = ud->size - *ud->pos - 1;
                memcpy(ud->buff + *ud->pos, ptr, len);
                *ud->pos += len;
                ud->buff[*ud->pos] = 0;
            }
        }
    }
    return size * nmemb;
}

static int http_request(const char* url, const char* json, void* userdata, long* http_status)
{
    std::call_once(_curl_init_flag, []() {
        curl_global_init(CURL_GLOBAL_DEFAULT);
    });

    CURLcode           res        = CURLE_OK;
    struct curl_slist* headerlist = NULL;
    CURL*              curl       = curl_easy_init();
    if (!curl)
    {
        printf("curl_easy_init error\n");
        goto ERR;
    }

    headerlist = curl_slist_append(headerlist, "Accept:application/json");
    headerlist = curl_slist_append(headerlist, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
    if (json)
    {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(json));
        curl_easy_setopt(curl, CURLOPT_POST, 1);
    }
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_USERAGENT,
                     "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0");
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, FALSE);

    curl_easy_setopt(curl, CURLOPT_READFUNCTION,  NULL);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_userdata);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA,     userdata);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL,      1);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, DEFAULT_CON_TIMEOUT_MS);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS,        DEFAULT_CON_TIMEOUT_MS);
    curl_easy_setopt(curl, CURLOPT_IPRESOLVE,         CURL_IPRESOLVE_V4);

    // start req
    res = curl_easy_perform(curl);
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_status);
    if (*http_status == 401)
        res = CURLE_REMOTE_ACCESS_DENIED;
ERR:
    // release curl
    curl_easy_cleanup(curl);
    curl_slist_free_all(headerlist);
    return res;
}

extern "C"
int http_transmit(const char* url, const char* json, char* r, int* rlen)
{
    int _rlen = 0;
    
    if (r != NULL)
    {
        r[0] = 0;
    }
    if (rlen != NULL)
    {
        _rlen = *rlen;
        *rlen = 0;
    }

    userdata_st ud    = {0};
    if (r != NULL && rlen != NULL && _rlen > 0)
    {
        ud.buff = r;
        ud.size = _rlen;
        ud.pos  = (unsigned int*)rlen;
    }

    long http_status;
    int  res = http_request(url, json, &ud, &http_status);

    if (res == CURLE_OK)
    {
        res = (int)http_status;
    }
    else
    {
        if (r != NULL && rlen != NULL && _rlen > 0)
        {
            int len = std::min((int)strlen(curl_easy_strerror((CURLcode)res)), _rlen - 1);

            strncpy(r, curl_easy_strerror((CURLcode)res), len);
            r[len] = 0;
        }
    }

    return res;
}

